#include "def.h"
#include <stdio.h>
#include <stdlib.h>
#include <cutil.h>
#include <cuda_runtime.h>
#include <cutil_inline.h>
#include <sys/time.h>

void LoadBMPFile(uchar4 **dst, int *width, int *height, const char *name);
void SaveBMPFile(uchar4 *dst, int width, int height, const char *outputname, const char *inputname);
void kernel_2d_convolution_gpu(uchar4 *_in, uchar4 *_out, int width, int height, int K, int SIZE);
void kernel_2d_convolution_cpu(uchar4 *p, uchar4 *o, int width, int height, int K);

   
typedef unsigned long long timestamp_t;

static timestamp_t get_timestamp () {
 struct timeval now;
 gettimeofday (&now, NULL);
 return  now.tv_usec + (timestamp_t)now.tv_sec * 1000000;
}

int main(int argc, char **argv)
{
  int width, height, size;
  uchar4 *cpusrc, *gpusrc, *gpuout, *tmp, *cpuout;
	
  printf("\nAn implementation of Anisotropic Diffusion\n");
  // Get inputs
  if(argc != 8)
    {
      printf("Usage : imgfilter inputfilename outputPrefix repeatingTimes colorDelta checkResult showStep sizeChuck\n");
      printf("Ex:\n");
      printf("imgfilter flower.bmp flowerOut 100 20 1 0 15\n");
      
      exit(0);
    }

  //printf("uchar4: %d, size: %d",sizeof(uchar4),size);
  
  int nTest = atoi(argv[3]);
  int K = atoi(argv[4]);
  int check_result = atoi(argv[5]);
  int show_step = atoi(argv[6]);
  int sizeChunk= atoi(argv[7]);

  char *oStr = (char *) malloc(50); 
  char *tmpStr = (char *) malloc(50);

  int base=1;//(nTest<11? 1: (nTest<101? 10: (nTest<1001? 100:1000)));

  // load input BMP file
  LoadBMPFile(&gpusrc, &width, &height, argv[1]);
  size = width*height*sizeof(uchar4);
  cpusrc = (uchar4*)malloc(size);
  gpuout = (uchar4*)malloc(size);
  cpuout = (uchar4*)malloc(size);

  memcpy(cpusrc, gpusrc, size);

  sprintf(oStr, "%s0.bmp", argv[2]);
  SaveBMPFile(gpusrc, width, height, oStr, argv[1]);

  unsigned int timer = 0;
//  printf("Timer starts...\n");
  cutilCheckError(  cutCreateTimer(&timer)  );

  int iter;
  float gpuTime=0,cpuTime=0,lastTime=0;

  tmp=gpusrc; gpusrc=gpuout; gpuout=tmp;
  tmp=cpusrc; cpusrc=cpuout; cpuout=tmp;
  
  // CPU Part
  if (check_result) {
    printf("\nRun algorithm on CPU... \n");
  
 //   tmp=src; src=cpuout; cpuout=tmp;

    for (iter=0; iter<nTest; iter++) {

      timestamp_t t0 = get_timestamp(); 
      if ((iter+1) % base == 0) {
        printf("\r%7d/%d ",iter+1,nTest);
        fflush(stdout);
      }
      tmp=cpusrc; cpusrc=cpuout; cpuout=tmp;
      kernel_2d_convolution_cpu(cpusrc, cpuout, width, height, K);
    
      timestamp_t t1 = get_timestamp(); 
      
      lastTime=(t1-t0)/1000000.0L;
      cpuTime+=lastTime;
      printf("%8.5f",lastTime);
    }

    printf("\nRuntime on CPU: %.5f sec\n",cpuTime);

    sprintf(oStr, "%sc.bmp", argv[2]);
    SaveBMPFile(cpuout, width, height, oStr, argv[1]);
  }

  //GPU Part

  printf("\nRun algorithm on GPU...\n");
  for(iter=0; iter<nTest; iter++) {
    if ((iter+1) % base == 0) {
      printf("\r%7d/%d ",iter+1,nTest);
      fflush(stdout);
    }

    tmp=gpusrc; gpusrc=gpuout; gpuout=tmp;
    cutilCheckError(  cutResetTimer(timer)    );
    cutilSafeCall( cudaThreadSynchronize() );
    cutilCheckError( cutStartTimer(timer) );

    kernel_2d_convolution_gpu(gpusrc, gpuout, width, height, K, sizeChunk);

    cutilSafeCall( cudaThreadSynchronize() );
    cutilCheckError( cutStopTimer(timer) );
   
      
    lastTime = (double) cutGetTimerValue(timer)/1000;
    gpuTime+=lastTime;
    printf("%8.5f",lastTime);

    if (show_step || iter==nTest-1) {
      sprintf(oStr, "%s%d.bmp", argv[2], iter+1);
      SaveBMPFile(gpuout, width, height, oStr, argv[1]);
    }
  }	           

  printf("\nRuntime on GPU: %.5f sec\n", gpuTime);
  printf("\nGPU is %4.2f times faster\n",cpuTime/gpuTime);	

  if(check_result) {
	  // check sanity
	  float err = 0,maxErr=0;;
	  for(int idx=0; idx<width*height; idx++)
	    {
	      float curerr= sqrt( (float)( ( (float)gpuout[idx].x -  (float)cpuout[idx].x)*( (float)gpuout[idx].x -  (float)cpuout[idx].x) +
					   ( (float)gpuout[idx].y -  (float)cpuout[idx].y)*( (float)gpuout[idx].y -  (float)cpuout[idx].y) +
					   ( (float)gpuout[idx].z -  (float)cpuout[idx].z)*( (float)gpuout[idx].z -  (float)cpuout[idx].z) +
					   ( (float)gpuout[idx].w -  (float)cpuout[idx].w)*( (float)gpuout[idx].w -  (float)cpuout[idx].w) ) );

	      err += curerr;			    
	    }
	  maxErr = max(maxErr, err/(float)(width*height));
    printf("Percent error : %f\%\n\n",  maxErr);
	}

  free(gpusrc);
  free(cpusrc);
  free(gpuout);
  free(cpuout);
  free(tmpStr);
  free(oStr);
  return 0;
}
